import serial
from threading import Thread
import time 
import struct
import json
import random
import redis
from paho.mqtt import client as mqtt_client
 

class TIce101:
    cmdBytes_0 = bytes([0x10 , 0x49 , 0x01 , 0x4A ,0x16 ])
    cmdBytes_1 = bytes([0x10 , 0x40 , 0x01 , 0x41 , 0x16 ])
    cmdBytes_callAllData_0 = bytes([0x68 , 0x09 , 0x09 , 0x68 , 0x53 , 0x01 , 0x64 , 0x01 , 0x06 , 0x01 , 0x00 , 0x00 , 0x14 , 0xD4 , 0x16 ])
    cmdBytes_callAllData_1 = bytes([0x68 , 0x09 , 0x09 , 0x68 , 0x73 , 0x01 , 0x64 , 0x01 , 0x06 , 0x01 , 0x00 , 0x00 , 0x14 , 0xF4 , 0x16 ])
    cmdByte_callLevel1_0 = bytes([0x10 , 0x5A , 0x01 , 0x5B , 0x16 ])
    cmdByte_callLevel1_1 = bytes([0x10 , 0x7A , 0x01 , 0x7B , 0x16 ])
    cmdByte_callLevel2_0 = bytes([0x10 , 0x5B , 0x01 , 0x5C , 0x16 ])
    cmdByte_callLevel2_1 = bytes([0x10 , 0x7B , 0x01 , 0x7C , 0x16 ])
    cmdByte_callPower_0 = bytes([0x68 , 0x09 ,0x09 ,0x68 ,0x53 ,0x01 ,0x65 ,0x01 ,0x06 ,0x01 ,0x00 ,0x00 ,0x01 ,0xC2 ,0x16 ])
    cmdByte_callPower_1 = bytes([0x68 , 0x09 ,0x09 ,0x68 ,0x73 ,0x01 ,0x65 ,0x01 ,0x06 ,0x01 ,0x00 ,0x00 ,0x01 ,0xE2 ,0x16 ])
    def __init__(self) -> None:
        self.comPort = "COM3"
        self.comRate = 2400

        self.redisHost="hh.hhdata.cn"        
        self.redisPort = 31271
        self.redisDB = 1 
        self.redisPwd = "hhuc1115"
        self.redisConnection = None
        self.redisPath = "/ice101"

        self.mqttClient = None
        self.mqttHost = "hh.hhdata.cn"
        self.mqttPort = 31274
        self.mqttUid = "admin"
        self.mqttPwd = "hhuc1115"
        self.mqttTopic = "lce/update"
        self.mqttConnected = False


        #  status 
        # 0 初始状态
        # 1 已查询链路状态，等待返回链路查询结果
        # 2 已复位链路，等待返回链路复位结果
        # 3 已总召 ， 等待总召响应
        # 4 已收到总召确认，等待总召开始
        # 5 总召开始
        # 6 总召结束
        self.status = 0 
        self.serial = None
        self.fcbFlag = 0
        self.pgkTypes = {}

        self.dataPoints ={}

        self.callAll_timeout = 120
        self.callAll_time = 0
        self.callPower_timeout = 180
        self.callPower_time = 0
        self.callLevel2_timeout = 2
        self.callLevel2_time = 0 
        self.callLevel1_flag = False


        self.dataValues={}


    def connect(self):
        res = False
        try:            
            self.serial = serial.Serial(self.comPort, self.comRate  )
            isOpen = self.serial.isOpen()
            if isOpen:
                res = True
                print("open com:"+ self.comPort+" success.")
            else:
                print("open com:"+ self.comPort+" faild.")
        except Exception as er :
            print(er)
        return res 
    def startAction(self):
        try:
            threadAction = Thread(target= self.threadAction)
            threadAction.setDaemon(True)
            threadAction.start()
        except Exception as er :
            print(er)
    def recPgk(self):
        res = 0 
        resBytes = []
        adc = 0 
        pgkType = 0
        ctrlCode = 0 
        pgk = None
        try:
            serial = self.serial
            recHeaderBytes = serial.read(1)
            if len(recHeaderBytes)>0: 
                recHeader = recHeaderBytes[0]
                if recHeader==0xE5:
                    res = 0xE5
                    self.log("r", bytearray([0xE5]))
                elif recHeader==0x10:
                    time.sleep(0.1)
                    count = serial.inWaiting()
                    #print("rec body size :"+str(count))
                    if count>= 4 or 1>0:
                        recBytes = serial.read(4) 
                        if( len(recBytes) >=4 and recBytes[3]==0x16):
                            resBytes.append(0x10)
                            ctrlCode = recBytes[0]
                            if  (ctrlCode>>5) >0 :
                                adc = 1
                            pgkType = ctrlCode & 0x0f
                            for i in recBytes:
                                resBytes.append(i)
                            res = 0x10
                elif recHeader == 0x68 :
                    res = 0x68
                    rec68HeadBytes = serial.read(3)
                    flag = len(rec68HeadBytes)>=3 and rec68HeadBytes[0]==rec68HeadBytes[1] and rec68HeadBytes[2]==0x68
                    if flag==True:
                        pgkSize = rec68HeadBytes[0]+2
                        rec68Bytes = serial.read(pgkSize)
                        if( len(rec68Bytes) >=pgkSize and rec68Bytes[pgkSize-1]==0x16):
                            resBytes.append(0x68)
                            resBytes.append(pgkSize)
                            resBytes.append(pgkSize)
                            resBytes.append(0x68)
                            ctrlCode = rec68Bytes[0]
                            if  (ctrlCode>>5) >0 :
                                adc = 1
                            pgkType = rec68Bytes[2]
                            for i in rec68Bytes:
                                resBytes.append(i)
                            res = 0x68
                            
                            self.pgkTypes[pgkType] = resBytes
                            ice101pgk = TIce101Pgk()
                            ice101pgk.decode(resBytes)
                            if ice101pgk.decodeFlag :
                                pgk = ice101pgk

                                tm = time.time()
                                for dpt in pgk.dataPoints:
                                    dtpInfo = {
                                        "a": dpt.addr ,
                                        "v": dpt.val ,
                                        "tm": tm ,
                                        "type":pgk.pgkType ,
                                        "cot": pgk.cot
                                    }
                                    self.dataPoints[dpt.addr] = dtpInfo
                                self.writeLog(pgk)
                                self.notify(pgk)
                            #print(ice101pgk)


                else:
                    res = -1 
        except Exception as er :
            print(er)
        self.log("r" , resBytes)
        return res , resBytes , adc , pgkType , ctrlCode , pgk
    def queryLinkStatus(self):
        try:
            cmdBytes = TIce101.cmdBytes_0
            self.serial.write(cmdBytes)
            self.log('s', cmdBytes)
            self.status =  1
        except Exception as er :
            print(er)
    def resetLink(self):
        try:
            cmdBytes = TIce101.cmdBytes_1
            self.serial.write(cmdBytes)
            self.log('s', cmdBytes)
            self.status = 2 
        except Exception as er :
            print(er)
    def callLevel1(self):
        try:
            cmdBytes = None
            if self.fcbFlag == 0 :
                self.fcbFlag = 1                
                cmdBytes = TIce101.cmdByte_callLevel1_1
            else:
                self.fcbFlag = 0
                cmdBytes = TIce101.cmdByte_callLevel1_0
            self.serial.write(cmdBytes)
            self.log('s', cmdBytes)
        except Exception as er :
            print(er)
    def callLevel2(self):
        try:
            cmdBytes = None
            if self.fcbFlag == 0 :
                self.fcbFlag = 1                
                cmdBytes = TIce101.cmdByte_callLevel2_1
            else:
                self.fcbFlag = 0
                cmdBytes = TIce101.cmdByte_callLevel2_0
            time.sleep(2)
            self.serial.write(cmdBytes)
            self.log('s', cmdBytes)
        except Exception as er :
            print(er)
    def callAll(self):
        try:
            cmdBytes = None
            if self.fcbFlag == 0 :
                self.fcbFlag = 1                
                cmdBytes = TIce101.cmdBytes_callAllData_1
            else:
                self.fcbFlag = 0
                cmdBytes = TIce101.cmdBytes_callAllData_0                
            self.serial.write(cmdBytes)
            self.log('s', cmdBytes)
            self.status = 3 
        except Exception as er :
            print(er)
    def callPower(self):
        try:
            cmdBytes = None
            if self.fcbFlag == 0 :
                self.fcbFlag = 1                
                cmdBytes = TIce101.cmdByte_callPower_1
            else:
                self.fcbFlag = 0
                cmdBytes = TIce101.cmdByte_callPower_0                
            self.serial.write(cmdBytes)
            self.log('s', cmdBytes)
            self.status = 3 
        except Exception as er :
            print(er)
    def threadAction(self):
        try:
            self.status = 0 
            connectFlag = self.connect()
            #self.connectMqtt()
            if connectFlag :
                self.queryLinkStatus()
                initFlag = False
                while 1>0:
                    try:
                        time.sleep(0.1)
                        recFlag , recPgkBytes , recAdc , recPgkType , recCtrlCode , recPgk= self.recPgk()
                        if not initFlag:
                            if recFlag == 0x10 and recCtrlCode==0x0b:
                                self.resetLink()
                            if recFlag == 0x10 and recCtrlCode==0x00:
                                initFlag = True
                                self.callAll()
                        else:
                            if recFlag == 0x68 and recCtrlCode == 0x08 and recPgkType==0x64:
                                self.callPower()
                            elif recAdc >0 :
                                self.callLevel1()
                            else:
                                self.callLevel2()
                    except Exception as ee:
                        print(ee)
                        break

        except Exception as er:
            print(er)
    def log(self , dir , dataBytes):
        log = dir + '-> '
        for i in dataBytes:
            log = log + ' ' + hex(i)
        print(log)
    def writeLog(self , pgk):
        try:
            if self.redisConnection == None:
                self.redisConnection = redis.Redis(
                    host=self.redisHost,
                    port=self.redisPort,
                    password=self.redisPwd,
                    db=self.redisDB,
                    decode_responses=True)
            if self.redisConnection!=None:
                for dp in pgk.dataPoints:
                    try:
                        dpKey = self.redisPath+"/"+str(pgk.pgkType)+"/"+str(dp.addr)
                        dpDict = dp.getDict()
                        dpDict["cot"] = pgk.cot
                        self.redisConnection.hmset(dpKey , dpDict)
                    except Exception as e1 :
                        print(e1) 
                #print("---------------json-----------------")
                #jsonRes  = pgk.getNotifyJson()
                #print(jsonRes)
                #print("---------------------------------")
        except Exception as er :
            print(er)
    def connectMqtt(self):
        try:
            if self.mqttClient==None:
                clientId = f'py-mqtt-{random.randint(0, 1000)}'
                self.mqttClient = mqtt_client.Client(clientId)
                self.mqttClient.username_pw_set("admin","hhuc1115") 
                def on_MqttConnect( client, userdata, flags, rc):
                    if rc == 0:
                        self.mqttConnected = True
                        self.startAction()
                        print("Connected to MQTT Broker!")
                    else:
                        print("Failed to connect, return code %d\n", rc)
                self.mqttClient.on_connect = on_MqttConnect
                #self.mqttClient.username_pw_set(self.mqttUid , self.mqttPwd) 
                #self.mqttClient.connect(self.mqttHost, self.mqttPort  ) 
                self.mqttClient.connect("hh.hhdata.cn", 31274 ) 
                self.mqttClient.loop_forever()
        
        except Exception as er:
            print(er) 
    def notify(self , pgk):
        try: 
            keys = self.dataValues.keys()
            notifyObj={
                "t":pgk.pgkType,
                "dps":{}
            }
            for dp in pgk.dataPoints:
                dpKey = str(pgk.pgkType)+"_"+str(dp.addr)
                if dpKey in keys:
                    oldVal = self.dataValues[dpKey]
                    if pgk.pgkType==1 and dp.val!=oldVal:
                        self.dataValues[dpKey] = dp.val
                        notifyObj["dps"][dpKey]  = dp.val    
                    elif pgk.pgkType==13 :
                        if oldVal==0  and dp.val!=0: 
                            self.dataValues[dpKey] = dp.val
                            notifyObj["dps"][dpKey]  = dp.val   
                        elif abs(oldVal)>0.0 and  abs( dp.val - oldVal)/oldVal >=0.05:
                            self.dataValues[dpKey] = dp.val
                            notifyObj["dps"][dpKey]  = dp.val   
                else:
                    self.dataValues[dpKey] = dp.val
                    notifyObj["dps"][dpKey]  = dp.val
            notiyJson =  json.dumps(notifyObj)
            print("---------------json-----------------")
            print(notiyJson)
            if self.mqttConnected :
                self.mqttClient.publish( self.mqttTopic , notiyJson , 0 , True)
            print("---------------"+str(len(notiyJson ))+"-----------------") 

        except Exception as er :
            print(er)


class TIce101Pgk: 
    def __init__(self) -> None:
        self.version = 101
        self.controlCode = 0
        self.decodeFlag = 0 
        self.linkAddr = 0 
        self.stationAddr = 0 
        self.pgkType = 0 
        self.cot = 0
        self.dataPointAddrType = 0 
        self.dataPointNum = 0 
        self.pgkTimeFlag = 0 
        self.dataPointTimeFlag = 0 
        self.qsFlag= 0 
        self.dataPoints =[]
    def decode( self , dataBytes):
        try:
            self.controlCode = dataBytes[4]
            self.linkAddr = dataBytes[5]
            self.pgkType = dataBytes[6]
            vsq = dataBytes[7]
            self.cot = dataBytes[8]
            self.stationAddr = dataBytes[9]
            if vsq & 0x80 >0 :
                self.dataPointAddrType  = 1
            self.dataPointNum = vsq & 0x7F
            types = TIce101Type.getTypes()
            flag = self.dataPointNum>0 and self.pgkType in types.keys()
            if flag :
                pgkType = types[self.pgkType]
                offset = 10
                headAddr = int.from_bytes(bytearray(dataBytes[offset: offset+2]),byteorder= "little")
                if  self.dataPointAddrType == 1:
                    offset = offset + 2
                for i in range(self.dataPointNum):
                    dp = TIce101DataPoint()
                    dp.pgkType = self.pgkType
                    self.dataPoints.append(dp)
                    dp.index = i 
                    # addr
                    if self.dataPointAddrType == 1 :
                        dp.addr = headAddr + i                         
                    else:
                        dp.addr = int.from_bytes(bytearray(dataBytes[offset: offset+2]),byteorder= "little")
                        offset = offset + 2
                    # data value
                    if pgkType.dataLen == 1:
                        dp.val = dataBytes[offset]
                        offset  = offset + 1
                    elif pgkType.dataLen > 0 :
                        dpDataBytes = dataBytes[offset : offset + pgkType.dataLen]
                        if pgkType.dataType == 2 or pgkType.dataType == 3 :
                            dp.val = int.from_bytes( dpDataBytes , byteorder='little')
                        elif pgkType.dataType == 4:
                            floatVal = struct.unpack("<f" ,bytearray(dpDataBytes))
                            dp.val = floatVal[0]
                        offset = offset + pgkType.dataLen
                    # QDS 
                    if pgkType.qsType == 1 :
                        dp.qs = dataBytes[offset+1]
                        offset = offset + 1
                    # dataPoint time 
                    if pgkType.dataPointTimeType >0 :
                        dp.timeBytes = bytearray(dataBytes[offset : offset + pgkType.dataPointTimeType])
                        if pgkType.dataPointTimeType == 3 :
                            dp.time = TIce101Type.getTime3(dp.timeBytes)
                        elif pgkType.dataPointTimeType == 7 :
                            dp.time = TIce101Type.getTime7(dp.timeBytes)
                        offset = offset + pgkType.dataPointTimeType
                if pgkType.pgkTimeType >0 :
                    pgkTimeBytes = bytearray(dataBytes[offset : offset + pgkType.pgkTimeType])
                    pgkTime =0
                    if pgkType.pgkTimeType == 3 :
                        pgkTime = TIce101Type.getTime3(pgkTimeBytes)
                    elif pgkType.pgkTimeType == 7 :
                        pgkTime = TIce101Type.getTime7(pgkTimeBytes)
                    for dp in self.dataPoints:
                        dp.time = pgkTime
                self.decodeFlag = 1

        except Exception as er :
            print( er)

    def getNotifyJson(self):
        res = ""
        try:
            pgkObj ={
                "t": self.pgkType,
                "dp":{}
            }
            for dp in self.dataPoints:
                pgkObj["dp"][dp.addr] = dp.val
            res = json.dumps(pgkObj)
        except Exception as er:
            print(er)
        return res
class TIce101Type:
    types=None
    def __init__(self) -> None:
        self.typeCode  = 0
        self.qsType = 0
        # 1-byte 2-int16  3-int64  4-float
        self.dataType = 0 
        self.dataLen = 1
        self.pgkTimeType = 0
        self.dataPointTimeType = 0
    def getTypes():
        try:
            types = TIce101Type.types
            if types == None :
                types = {}

                type01 = TIce101Type()
                type01.typeCode = 0x01
                type01.qsType = 0 
                type01.dataType = 1  #byte
                type01.dataLen = 1
                type01.pgkTimeType = 0 
                type01.dataPointTimeType = 0 
                types[type01.typeCode] = type01

                type0D = TIce101Type()
                type0D.typeCode = 0x0D
                type0D.qsType = 1 
                type0D.dataType = 4  #float
                type0D.dataLen = 4
                type0D.pgkTimeType = 0 
                type0D.dataPointTimeType = 0 
                types[type0D.typeCode] = type0D

                type0F = TIce101Type()
                type0F.typeCode = 0x0F
                type0F.qsType = 1 
                type0F.dataType = 3  #int64
                type0F.dataLen = 4
                type0F.pgkTimeType = 0 
                type0F.dataPointTimeType = 0 
                types[type0F.typeCode] = type0F

                TIce101Type.types  = types
        except Exception as er :
            print(er)
        return TIce101Type.types
    def getTime3(bytes):
        return 0
    def getTime7(bytes):
        return 0
         
class TIce101DataPoint:
    def __init__(self) -> None:
        self.index = 0 
        self.time = 0
        self.timeBytes =None
        self.addr = 0 
        self.qs = 0 
        self.val =0
        self.pgkType = 0 
    def getDict(self): 
        index = self.addr
        if index > 0x6400:
            index = index - 0x6400
        elif index >0x4000:
            index = index - 0x4000 
        if self.time==0:
            self.time = time.time()
        return {
            "index":index ,
            "time": self.time ,
            "addr": self.addr ,
            "qs": self.qs ,
            "val": self.val,
            "type": self.pgkType
        }

#ice = TIce101Master()
#ice.start()


ice101 = TIce101()
ice101.connectMqtt()
#ice101.startAction()

running = True
while running:
    inputCmd = input("please input command:")
    if inputCmd == "exit":
        running = False
print("ice is disconnect.")

